#include <sys/stat.h>
#include <sys/time.h>
#include <errno.h>
#include <time.h>
#include <stdio.h>
#include <fcntl.h>
#include <string.h>
#include <stdlib.h>
#include <sys/times.h>
#include <sys/types.h>
#include <termios.h>
#include <unistd.h>
#include <sys/ioctl.h>
#include <sys/wait.h>
#include <string.h>

#define XMODEM_SOH 0x01
#define XMODEM_STX 0x02
#define XMODEM_EOT 0x04
#define XMODEM_ACK 0x06
#define XMODEM_NAK 0x15
#define XMODEM_CAN 0x18
#define XMODEM_CRC_CHR	'C'
#define XMODEM_CRC_SIZE 2		/* Crc_High Byte + Crc_Low Byte */
#define XMODEM_FRAME_ID_SIZE 2 		/* Frame_Id + 255-Frame_Id */
#define XMODEM_DATA_SIZE_SOH 128  	/* for Xmodem protocol */
#define XMODEM_DATA_SIZE_STX 1024 	/* for 1K xmodem protocol */
#define USE_1K_XMODEM 1  		/* 1 for use 1k_xmodem 0 for xmodem */
#define	TIMEOUT_USEC	0
#define	TIMEOUT_SEC		10

#if (USE_1K_XMODEM)
	#define XMODEM_DATA_SIZE 	XMODEM_DATA_SIZE_STX
	#define XMODEM_HEAD		XMODEM_STX
#else
	#define XMODEM_DATA_SIZE 	XMODEM_DATA_SIZE_SOH
	#define XMODEM_HEAD 		XMODEM_SOH
#endif


#define SERIAL_DEVICE "/dev/ttyS1"

#define GET_TIME()  { gettimeofday(&time_m, NULL); \
			time_m.tv_sec += TIMEOUT_SEC;\
		    }
#define GET_TIME_TIMEOUT(_time_out_)  { gettimeofday(&time_m, NULL); \
			time_m.tv_sec += _time_out_;\
		    }

#define CHK_TIME()  { gettimeofday(&time_n, NULL); \
			if(time_n.tv_sec > time_m.tv_sec) { \
				printf("\ntimeout!!!\n\n");\
				close(fd); \
				return -1; \
			} \
		     }
#define CHK_TIME_BREAK()  { gettimeofday(&time_n, NULL); \
			if(time_n.tv_sec > time_m.tv_sec) { \
				timeout_sign = 1; 	\
				printf("\ntimeout!!!\n\n");\
				break; \
			} \
		     }


#define CHK_TIME1()  { gettimeofday(&time_n, NULL); \
			if(time_n.tv_sec > time_m.tv_sec) { \
				printf("\ntimeout!!!\n\n");\
				if(datafile != NULL) \
					fclose(datafile); \
				return -1; \
			} \
		     }

typedef struct {
	unsigned int ih_magic;
	unsigned int ih_checksum;
	unsigned int ih_time;
	unsigned int ih_size;
	unsigned int ih_reserved;
	unsigned int pkg_flash_addr;
	unsigned int pkg_run_addr;
	unsigned int ih_hcrc;
} uc_image_header_t;

int xmodem_send(int fd, char *fname);
int upgrade(int argc,char *argv[]);


ssize_t deal_read(int fd, void *buf, size_t count)
{
	int ret = 0;

#if 0
	ret = read(fd, buf, count);
	return ret;
#else
	printf("deal_read \r\n");
	while (1)
	{
		ret = read(fd, buf, count);
		if (ret == 0)
		{
			printf("read serial return 0, please check serial device.\n");
			exit(-10);
		}
		if(ret < 0)
		{
			if ((errno == EAGAIN) || (errno == EINTR))
			{
				printf("read serial return -1, errno = %d, retry.\n", errno);
				continue;
			}
			else 
			{
				printf("read serial return -1, errno = %d, please check serial device.\n", errno);
				exit(-11);
			}
		}
		return ret;
	}
#endif
}

unsigned short get_crc16 ( char *ptr, unsigned short count )
{
	unsigned short crc, i;

	crc = 0;
	while(count--)
	{
		crc = crc ^ (int) *ptr++ << 8;

		for(i = 0; i < 8; i++)
		{
			if(crc & 0x8000)
				crc = crc << 1 ^ 0x1021;
			else
				crc = crc << 1;
		}
	}

	return (crc & 0xFFFF);
}

void dump(void *buf, int len)
{
	unsigned char *p = (unsigned char *)buf;
	int i;

	for(i = 0; i < len; i++) {
		if(i % 16 == 0) printf("%04x:", i);
		if(i % 16 == 8) printf(" -");
		printf(" %02x", p[i]);
		if(i % 16 == 15) printf("\n");
	}
	if(i % 16) printf("\n");
}

unsigned int get_file_size(const char * name)
{
	struct stat statbuff;
	//unsigned int size, checksum;
	if(stat(name, &statbuff) < 0)
		return -1;
	else
		return statbuff.st_size;
}


int check_file(char * fname, unsigned int magic)
{
	FILE *fd;
	uc_image_header_t header;
	unsigned int buf[1024];
	unsigned int fsize, checksum, i;
	unsigned short crc;
	size_t rByte;

	if((fd=fopen(fname,"rb"))==NULL)
	{
		printf("\n can't open this file or not exist! \n");
		return -1;
	}

	fsize = get_file_size(fname);

	printf("file size [%d]\n",fsize);


	while(fsize > sizeof(header)) {
		rByte = fread((char *)&header, sizeof(char), sizeof(header), fd);

		dump((char *)&header, sizeof(header));

#if 0
		printf("%04x\n,%04x\n,%04x\n,%04x\n,%04x\n,%04x\n,%04x\n,%04x\n",
				header.ih_magic,
				header.ih_checksum,
				header.ih_time,
				header.ih_size,
				header.ih_reserved,
				header.pkg_flash_addr,
				header.pkg_run_addr,
				header.ih_hcrc);
#endif
		crc = get_crc16 ( (char *) &header, sizeof(header)-4);
		printf("crc16  [%08x]\n", crc);

		if((header.ih_hcrc & 0xFFFF) != crc) {
			fclose(fd);
			return -1;
		}

		fsize -= sizeof(header);
		fsize -= header.ih_size;

		checksum = 0;
		while(header.ih_size > 0)
		{
			if(header.ih_size >= 1024 )
				rByte = 1024;
			else
				rByte = header.ih_size;

			memset(buf, 0, sizeof(buf));
			rByte = fread((char *)buf, 1, rByte, fd);
			for(i = 0; i < (rByte+3)/4; i++)
				checksum += buf[i];

			header.ih_size -= rByte;
		}

		printf("checksum  [%08x]\n\n",checksum);

		if( checksum != header.ih_checksum) {
			fclose(fd);
			return -1;
		}
	}

	fclose(fd);
	return 0;
}

static int select_read( int fd, int timeout ) //1ms
{
	fd_set set;
	struct timeval t;
	int ret;
  int i = timeout * 10;
  
	do{
	FD_ZERO(&set);
	FD_SET(fd, &set);
	t.tv_sec = 0;
	t.tv_usec = 100;

	ret = select(FD_SETSIZE, &set, NULL, NULL, &t );
	if(ret == 0) continue;
	if(ret < 0 && errno == EINTR)continue;
	else return ret;
	}while(i--);
	return ret;
}


int set_baudrate(int fd, int baudrate)
{
	struct termios options, oldtio;

	if(fcntl(fd, F_SETFL, 0) < 0) {
		printf("fcntl failed!\n");
		return -1;
	}

	if(tcgetattr(fd, &oldtio) != 0) {
		printf("setup serial error!\n");
		return -1;
	}

	/* Get the current options for the port... */
	tcgetattr(fd, &options);

	/* Set the baud rates to baudrate... */
	cfsetispeed(&options,baudrate);
	cfsetospeed(&options,baudrate);
	tcsetattr(fd, TCSANOW, &options);

	if (0 != tcgetattr(fd, &options))
	{
		printf("get options error!\n");
		return -1;
	}

	/*
	 * 8bit Data,no partity,1 stop bit...
	 */
	options.c_cflag &= ~PARENB;//无奇偶校验
	options.c_cflag &= ~CSTOPB;//停止位，1位
	options.c_cflag &= ~CSIZE; //数据位的位掩码
	options.c_cflag |= CS8;    //数据位，8位

#if 0
	/* inter-character timer, timeout VTIME * 0.1 */
	options.c_cc[VTIME] = 0;
	/* blocking read until VMIN character arrives */
	options.c_cc[VMIN] = 0;

	tcflush(fd,TCIFLUSH);

	/* Choosing Raw Input */
	options.c_lflag &= ~(ICANON | ECHO | ECHOE | ISIG);
	options.c_oflag &= ~OPOST;
#endif
	cfmakeraw(&options);

	/*
	 * Set the new options for the port...
	 */
	if (tcsetattr(fd, TCSANOW, &options) != 0)
	{
		printf("setup serial error!\n");
		return -1 ;
	}

	return 0 ;
}

int initial_serialPort(char * serial_device)
{
	int fd;
	//struct termios options, oldtio;
	//fd = open( serial_device , O_RDWR | O_NOCTTY  | O_NDELAY  );
	fd = open( serial_device , O_RDWR );
	if ( fd == -1 )
	{
		/* open error! */
		printf("Can't open serial port!\n");
		return -1;
	}

	set_baudrate(fd, B9600);

	return fd ;
}

int main(int argc, char *argv[])
{
	int i = 0, retry_num = 0, ret = 0;

	setbuf(stdout, NULL);
	if((argc != 4) && (argc != 5))
	{
		printf("Usage:%s\n",argv[0]);
		printf("eg:");
		printf("./serial bootloader.pkg firmware.pkg /dev/ttyS0 [retry_num]\n");
		exit(-1);
	}
	else
	{
		printf("===== FIREBIRD FIRMWARE UPGRADE =====\n");
	}

	printf("checking file: %s\n", argv[1]);

	if(check_file(argv[1], 0xf0f1f2f3)) {
		printf("file error!\n");
		return -1;
	}

	printf("checking file: %s\n", argv[2]);
	if( !((0 == check_file(argv[2], 0xf0f1f2f3)) || (0 == check_file(argv[2], 0xf3f4f5f6)) )) {
		printf("file error!\n");
		return -1;
	}

	if (argc > 4)
	{
		retry_num = argv[4][0] - '0';
	}
	printf("retry_num is %d\n", retry_num);

	for (i = 0; i <= retry_num; i ++)
	{
		ret = upgrade(argc-1, argv);
		if (ret == 0)
			break;
	}

	return ret;
}

/*****************************************************************************/
/*****************************************************************************/
int upgrade(int argc,char *argv[])
{
	int fd;
	struct timeval time_m, time_n;
	int rByte = 0;
	char name[100];
	char rbuf[1000];
	char keyword[128];
	int len_keyword = 0, timeout_sign = 0;

/*
	setbuf(stdout, NULL);
	if(argc != 4)
	{
		printf("Usage:%s\n",argv[0]);
		printf("eg:");
		printf("./serial bootloader.pkg firmware.pkg /dev/ttyS0\n");
		exit(-1);
	}
	else
	{
		printf("===== FIREBIRD FIRMWARE UPGRADE =====\n");
	}

	printf("checking file: %s\n", argv[1]);

	if(check_file(argv[1], 0xf0f1f2f3)) {
		printf("file error!\n");
		return -1;
	}

	printf("checking file: %s\n", argv[2]);
	if( !((0 == check_file(argv[2], 0xf0f1f2f3)) || (0 == check_file(argv[2], 0xf3f4f5f6)) )) {
		printf("file error!\n");
		return -1;
	}
*/

	// open serial port B9600
	if ((fd = initial_serialPort(argv[3])) == -1)
	{
		printf("Can't open COM\n");
		return -1 ;
	}

	usleep(5000);

	sprintf(name,"$CFGPRT,1,0,115200,3,3\n");
	rByte = write( fd, name, strlen(name));
	tcdrain(fd);
	usleep(200000);

	set_baudrate(fd, B115200);
	usleep(200000);
	tcflush(fd, TCIFLUSH);

	sprintf(name,"$PDTINFO\r\n");
	rByte = write( fd, name, strlen(name));
	usleep(2000 * 1000);

	rByte = 0;
	if(select_read(fd,1) > 0) {
		usleep(50000);

		//rByte = read(fd,&rbuf,sizeof(rbuf)-1);
		rByte = deal_read(fd,&rbuf,sizeof(rbuf)-1);

		//write_number = write( 1, rbuf, rByte);
		rbuf[rByte] = 0;
		printf( "%s", rbuf);
	}
	printf("rByte = %d...\n", rByte);
	
	//1. firmare is not ok, send menu reset cmd
	sprintf(name,"5\r\n");
	rByte = write( fd, name, 3);
	usleep(50000);
	rByte = write( fd, name, 3);
	usleep(50000);
	rByte = write( fd, name, 3);
	usleep(50000);
	//2. firmare is ok, send RESET cmd
	//add hardware reset or power on code here according to your hardware
	//sprintf(name,"$RESET,hf0,hff\n");
	sprintf(name,"$RESET,hf0,hff\n");
	rByte = write( fd, name, strlen(name));
	rByte = write( fd, name, strlen(name));//change 1: repeat

#if 0
	if(select_read(fd,1) > 0) {
		usleep(5000);

		rByte = read(fd,&rbuf,sizeof(rbuf)-1);
		//write_number = write( 1, rbuf, rByte);
		rbuf[rByte] = 0;
		printf( "%s", rbuf);
	}
#endif

	printf("waiting for YC, timeout is %d s\n", TIMEOUT_SEC);

	int i = 0;

	sprintf(keyword, "%s", "YC");
	len_keyword = strlen(keyword);

	GET_TIME();
	memset(name, 0, sizeof(name));
	sprintf(name,"M!T");
	while(1) {
		CHK_TIME_BREAK(); //
		rByte = write( fd, name, strlen(name)); 
		rByte = select_read(fd,1);
		if(rByte <= 0)
			continue;
		printf("Test: select_read return value is : [%d]\n", rByte);

		//rByte = read(fd, rbuf + len_keyword, sizeof(rbuf) - len_keyword);
		rByte = deal_read(fd, rbuf + len_keyword, sizeof(rbuf) - len_keyword);
		rByte += len_keyword;
		rbuf[rByte] = 0;
		for (i = 0 ; i < rByte; i++)
		{
			if (rbuf[i] == 0)
				rbuf[i] = 0x1;
			printf(", %c+0x%02x", rbuf[i], rbuf[i]);
		}
		printf("\n");

		if(strstr(rbuf, keyword)) //add condition
			break;

		strcpy(rbuf, rbuf + rByte - len_keyword);
	}
	//wait YC timeout deal
	if (timeout_sign == 1)
	{
		//wait NAK
		GET_TIME();
		while(1)
		{
			CHK_TIME();
			if(select_read(fd,1) <= 0)
				continue;
	
			rByte = deal_read(fd, rbuf,sizeof(rbuf));
			if (rbuf[rByte-1] == 'C') 
			{
				printf("###read xmodem start character 'C'.\n");
				break;
			}
		}
	}

	printf("download bootloader...\n");

	printf("bootloader: %s\n", argv[1]);

	if(xmodem_send(fd, argv[1])) {
		printf("xmodem error!\n");
		close(fd);
		return -2;
	}
	GET_TIME();
	while(1) {
		CHK_TIME();
		if(select_read(fd,1) > 0)
			usleep(50000);
		else
			continue;

		//rByte = read(fd,&rbuf,sizeof(rbuf)-1);
		rByte = deal_read(fd,&rbuf,sizeof(rbuf)-1);
		//rByte = write( 1, rbuf, rByte);
		rbuf[rByte] = 0;
		printf( "%s", rbuf);

		if(strstr(rbuf,"boot>") != NULL)
			break;

	}

	printf("download bootloader success\n");
	printf("Download firmware...\n");

	sprintf(name,"2\r");
	rByte = write( fd, name, strlen(name));

	GET_TIME();
	while(1) {
		CHK_TIME();
		rByte = write( fd, name, strlen(name));
		if(select_read(fd,1) > 0)
			usleep(100000); //change from 500 ms to 100 ms
		else
			continue;

		//rByte = read(fd,&rbuf,sizeof(rbuf)-1);
		rByte = deal_read(fd,&rbuf,sizeof(rbuf)-1);
		//rByte = write( 1, rbuf, rByte);
		rbuf[rByte] = 0;
		printf( "%s", rbuf);

		if(strstr(rbuf,"begin to update flash from uart") != NULL)
			break;
	}

	if(xmodem_send(fd, argv[2]) < 0) {
		printf("xmodem error! upgrade failed!\n");
		close(fd);
		return -2;
	}

	printf("Download firmware success\n");
	usleep(200 * 1000);

	sprintf(name,"5\r\n");
	rByte = write( fd, name, 3);
	usleep(50000);
	rByte = write( fd, name, 3);
	usleep(50000);
	rByte = write( fd, name, 3);
	usleep(50000);

	set_baudrate(fd, B9600);
	GET_TIME();
	while(1) {
		CHK_TIME();
		if(select_read(fd,1) > 0)
			usleep(50000);
		else
			continue;

		//rByte = read(fd,&rbuf,sizeof(rbuf)-1);
		rByte = deal_read(fd,&rbuf,sizeof(rbuf)-1);
		//rByte = write( 1, rbuf, rByte);
		rbuf[rByte] = 0;
		printf( "%s", rbuf);
		if(strstr(rbuf,"$GNRMC") != NULL)
			break;
	}

	printf("\n===== THE UPGRADE IS SUCCESSFUL, reseting=====\n");

	close(fd);
	return 0;
}
int xmodem_send(int fd, char *fname)
{
	char packet_data[XMODEM_DATA_SIZE];
	char frame_data[XMODEM_DATA_SIZE + XMODEM_CRC_SIZE + XMODEM_FRAME_ID_SIZE + 1];

	FILE *datafile;
	int complete,retry_num,pack_counter,read_number,write_number,i;
	unsigned short crc_value;
	unsigned char ack_id = 'C';
	struct timeval time_m, time_n;
	//char  name[100]={};

	datafile = NULL;
	pack_counter = 0;	// 包计数器清零
	complete = 0;
	retry_num = 0;

	printf("[%s]\n",fname);
	//只读方式打开一个准备发送的文件，如果不存在就报错，退出程序。
	if((datafile=fopen(fname,"rb"))==NULL)
	{
		printf("\n can't open this file or not exist! \n");
		return -1;
	}
	else
	{
		printf("Ready to send the file:%s\n",fname);
	}
#if 1
	printf("Waiting for signal C/NAK!\n");

	GET_TIME();
	while(1)
	{
		CHK_TIME1();
		if(select_read(fd,1) > 0)
			usleep(10000);
		else
			continue;

		//read(fd,&ack_id,1);
		deal_read(fd,&ack_id,1);
		if(ack_id == 'C')
			break;
	}

	printf("The signal NAK: %02x ok!!!\n",ack_id);//打印接收到的NAK信息
#endif

	while(!complete)
	{
		switch(ack_id)
		{
			case XMODEM_CRC_CHR:	// 接收到字符'C'开始启动传输，并使用CRC校验
				printf("begining to Send file %s...\n",fname);

			case XMODEM_ACK:        //0x06
				retry_num = 0;
				pack_counter++;

				read_number = fread(packet_data, sizeof(char), XMODEM_DATA_SIZE, datafile);
				//从打开的datafile指向的文件中读取
				//XMODEM_DATA_SIZE 个（char）数据，
				//放到packet_data这个数组中
				if(read_number > 0)//read_number为返回的读取实际字节数
				{
					//printf("test:read_number:%d\n", read_number);
					if(read_number < XMODEM_DATA_SIZE_STX)
					{
						printf("Start filling the last frame!\n");
						for(; read_number < XMODEM_DATA_SIZE; read_number++)
							packet_data[read_number] = 0x1A;  // 不足128字节用0x1A填充
						//printf("replenish data.\n");
					}

					frame_data[0] = XMODEM_HEAD;  // 帧开始字符
					frame_data[1] = (char)pack_counter;  // 信息包序号
					frame_data[2] = (char)(255 - frame_data[1]);  // 信息包序号的补码

					for(i=0; i < XMODEM_DATA_SIZE; i++)  // 128字节的数据段
						frame_data[i+3] = packet_data[i];//把收到的字符和信息头一起打包

					crc_value = get_crc16(packet_data, XMODEM_DATA_SIZE); // 16位crc校验
					frame_data[XMODEM_DATA_SIZE+3] = (unsigned char)(crc_value >> 8);// 高八位数据
					frame_data[XMODEM_DATA_SIZE+4] = (unsigned char)(crc_value);     //低八位数据

					/* 发送133字节数据 */
					write_number = write( fd, frame_data, XMODEM_DATA_SIZE + 5);//向串口写一个包数据，即133字节数据
					printf("."); //ADD: process
					fflush(stdout);
					//printf("the %d frame,",pack_counter);
					//printf("The %d pack(frame size:%d)has been sended!!\n",pack_counter,write_number);
					//printf("waiting for next ACK... \n......\n");

					GET_TIME();
					while(1)
					{
						CHK_TIME1();
						if(select_read(fd,1) > 0)
							usleep(10000);
						else
							continue;

						//read(fd,&ack_id,1);
						deal_read(fd,&ack_id,1);
						break;
					}

					if(ack_id == XMODEM_ACK) {
						//printf("ACK Ok!!Ready sending next pack!\n");
						//printf(" Ok!\n");
						;
					}
					else
					{
						printf("ACK Error!\n");
						printf("0x%02X\n",ack_id);
						//printf("pack_counter = %d\n", pack_counter);
					}
				}

				else  // 文件发送完成
				{
					ack_id = XMODEM_EOT;
					complete = 1;
					printf("Complete ACK\n");

					GET_TIME();
					while(ack_id != XMODEM_ACK)
					{
						CHK_TIME1();
						ack_id = XMODEM_EOT;
						write_number = write(fd,&ack_id,1);
						//while((read(fd, &ack_id, 1)) <= 0);
						while((deal_read(fd, &ack_id, 1)) <= 0);
					}
					printf("Send file successful!!!\n");
					fclose(datafile);
					datafile = NULL;
				}
				break;

			case XMODEM_NAK:
				if( retry_num++ > 10)
				{
					printf("Retry too many times,Quit!\n");
					complete = 1;
				}
				else //重试，发送
				{
					write_number = write(fd, frame_data, XMODEM_DATA_SIZE + 5);
					printf("Retry for ACK,%d,%d...", pack_counter, write_number);

					//					while((read(fd, &ack_id, 1)) <= 0);
					GET_TIME();
					while(1)
					{
						CHK_TIME1();
						if(select_read(fd,1) > 0)
							usleep(100);
						else
							continue;

						//read(fd,&ack_id,1);
						deal_read(fd,&ack_id,1);
						break;
					}

					if( ack_id == XMODEM_ACK )
						printf("OK\n");
					else
						printf("Error!\n");
				}
				break;
			default:
				printf("Fatal Error! %d\n", ack_id);
				complete = 1;
				return -1;
				break;
		}
	}



	if( datafile != NULL )
		fclose(datafile);

	return 0;
}

